Repository navigation
fix: don't charge CoinJoin participants for failures the masternode can't attribute to them - #7568
PastaPastaPasta wants to merge 3 commits into
Conversation
|
✅ Final review complete — no blockers (commit 2f9a7dc) · triage: normal |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2bc5dbd248
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughCoinJoin session denomination is now atomic, and validation uses captured denomination values. Server collateral state is consolidated in a lock-protected container. Pool checks, message handling, entry admission, finalization, and timeout processing use session snapshots and lock coordination. Fee selection supports probabilistic and guaranteed-abort policies. Tests cover collateral selection, timeout ordering, in-flight messages, pool locking, session recovery, and concurrent denomination validation. Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CoinJoinClient
participant CCoinJoinServer
participant cs_coinjoin
participant SessionCollaterals
participant Mempool
CoinJoinClient->>CCoinJoinServer: submit entry or signing message
CCoinJoinServer->>cs_coinjoin: capture and validate session state
CCoinJoinServer->>SessionCollaterals: check or store collateral
CCoinJoinServer->>cs_coinjoin: finalize session or update state
CCoinJoinServer->>Mempool: process final transaction
Possibly related PRs
Suggested reviewers: Mergeability Score: ⚪ Minimal · up to The PR adds collateral charging for non-cooperative aborted sessions while preserving free queue timeouts and expanding test coverage. No actionable merge-blocking risk remains after normal checks and review. Pre-merge checks:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/coinjoin/coinjoin.h (1)
345-350: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCorrect the logging explanation.
tinyformatformatsstd::atomic<int>through its implicit conversion toint.LogPrint()accepts arguments byconstreference, which avoids copying the non-copyable atomic.WalletCJLogPrint()forwards toCWallet::WalletLogPrintf, whose parameters are passed by value, so.load()is required to pass anint.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/coinjoin/coinjoin.h` around lines 345 - 350, Update the comments above nSessionDenom to accurately explain that tinyformat uses the atomic’s implicit int conversion, LogPrint() accepts it by const reference without copying, and WalletCJLogPrint() forwards to CWallet::WalletLogPrintf with by-value parameters, requiring an explicit .load().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/coinjoin/coinjoin.h`:
- Around line 345-350: Update the comments above nSessionDenom to accurately
explain that tinyformat uses the atomic’s implicit int conversion, LogPrint()
accepts it by const reference without copying, and WalletCJLogPrint() forwards
to CWallet::WalletLogPrintf with by-value parameters, requiring an explicit
.load().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7c98ea00-f5ed-46f1-b28d-035fcce2d4d4
📒 Files selected for processing (5)
src/coinjoin/client.cppsrc/coinjoin/coinjoin.hsrc/coinjoin/server.cppsrc/coinjoin/server.hsrc/test/coinjoin_inouts_tests.cpp
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The guaranteed abort-fee policy can still consume collateral from honest submissions already being processed, and a separate TRY_LOCK interleaving can reset sessions that remain recoverable. The commit stack also contains four syntactically unbuildable intermediate commits, while the offender-deduplication behavior change is obscured by a refactor-only commit subject.
Source: reviewer backends: gpt-5.6-sol (general), gpt-5.6-sol (dash-core-commit-history); final verifier backend: gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and is not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 3 blocking | 🟡 1 suggestion(s)
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/server.cpp`:
- [BLOCKING] src/coinjoin/server.cpp:625-628: Exclude in-flight submissions from guaranteed timeout penalties
`SelectCollateralToCharge()` only recognizes entries already committed to `vecEntries`, but `AddEntry()` releases `cs_coinjoin` while running `IsCollateralValid()` and `IsValidInOuts()` at lines 759-794. A valid `DSVIN` whose processing began before the deadline can therefore still be validating when the scheduler observes the timeout. With three reservations, two committed entries, and the third entry in flight, the third participant is classified as the only missing submitter, selected with certainty, and charged after `SetNull()` makes its final session revalidation fail. Signing has the same gap while `DSSIGNFINALTX` is being decoded or between its per-input `AddScriptSig()` calls. Track in-flight messages for the current session or serialize the timeout cutoff with their complete processing before applying a guaranteed collateral penalty.
- [BLOCKING] src/coinjoin/server.cpp:610-628: Preserve recoverable finalization when the preceding pool check is skipped
`CheckTimeout()` assumes the immediately preceding `CheckPool()` handled every recoverable accepting-entry timeout, but `CheckPool()` uses a non-blocking `TRY_LOCK`. A message-handling thread can hold `cs_check_pool`, sample `HasTimedOut()` as false immediately before the deadline, and then release the mutex after the scheduler's `CheckPool()` has skipped it but before the scheduler calls `CheckTimeout()`. `CheckTimeout()` then acquires the mutex after the deadline and unconditionally resets the session. If the session has at least `GetMinPoolParticipants()` committed entries but fewer entries than reservations, it should enter `ChargeAndFinalize` and retain the probabilistic policy; this interleaving instead aborts it and applies `GUARANTEED_ON_ABORT`. Re-evaluate the full accepting-entry action after acquiring `cs_check_pool` rather than relying on a preceding check that may have been skipped or sampled an earlier time.
- [BLOCKING] src/coinjoin/server.cpp:634: Fold the stray-brace correction into its introducing commit
Commit `67c9647ed03` introduces an extra closing brace immediately after `CCoinJoinServer::CheckTimeout()`. The unmatched brace remains in `264ba3fdfa7`, `76e366e1ceb`, and `9e199087637`, and is only removed by the final commit `2bc5dbd248b`. Those four intermediate commits are syntactically unbuildable and unusable as `git bisect` points. Amend `67c9647ed03` to omit the extra brace, remove the corrective deletion from the final commit, and rebase the intervening commits so every permanent-history state builds independently.
- [SUGGESTION] src/coinjoin/server.cpp:503-509: Make offender deduplication explicit in the commit history
Commit `9e199087637` is titled `refactor: separate CoinJoin offender selection policy`, but it also changes observable behavior: signing offenders are added once per entry instead of once per unsigned input. This removes the extra random-selection weight previously assigned to participants with multiple unsigned inputs. Move the deduplication hunk into the behavioral fix commit or reword `9e199087637` and add a commit body explaining the weighting change so `git log` and `git blame` expose the functional change.
2bc5dbd to
3d5cf6b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d5cf6b6e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
All four prior verified findings are resolved at the current head, but two signing-timeout paths still block approval: guaranteed abort charging can penalize honest participants after the server tells them to stop, and a stale pool-check interleaving can discard a fully signed transaction. The stack also has two non-blocking history issues: the lint-only follow-up should be folded into its introducing commits, and the subtle timeout fixes should retain their rationale in commit bodies.
Source: reviewer backends: gpt-5.6-sol (general), gpt-5.6-sol (dash-core-commit-history); final verifier backend: gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 2 blocking | 🟡 2 suggestion(s)
2 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/server.cpp`:
- [BLOCKING] src/coinjoin/server.cpp:681-684: Do not guarantee a fee after the coordinator aborts signing
The guaranteed timeout policy applies even after the server has instructed connected clients to stop signing. `RelayFinalTransaction()` calls `RelayStatus(STATUS_REJECTED)` when any entry is disconnected, and `ProcessDSSIGNFINALTX()` does the same after any `AddScriptSig()` failure. Connected clients process that rejection by entering `POOL_STATE_ERROR` and releasing their session resources, but the server remains in `POOL_STATE_SIGNING` unless every entry is disconnected. A malicious participant can exploit this by submitting its valid signature and then resubmitting it: the duplicate fails, every honest peer is told to abort, and the malicious participant is excluded from the unsigned-offender set. At timeout, this block then guarantees that one honest participant is charged. A failed `DSFINALTX` relay can similarly charge a connected entry that never received the transaction. Coordinator-originated signing aborts must reset without the guaranteed fee, or eligibility must be limited to participants that received the final transaction and were not subsequently instructed to abort.
- [BLOCKING] src/coinjoin/server.cpp:669-684: Commit complete signatures when rechecking a timeout
`CheckTimeout()` re-evaluates recoverable accepting-entry sessions but does not re-evaluate `IsSignaturesComplete()` for signing sessions. A scheduler `CheckPool()` can acquire `cs_check_pool`, observe the signatures as incomplete, and release `cs_coinjoin`. The final on-time `DSSIGNFINALTX` can then add its signature, skip its own `CheckPool()` because the scheduler still owns `cs_check_pool`, and clear its in-flight guard. When the scheduler subsequently enters `CheckTimeout()` after the deadline, there are no unsigned offenders, but this block still calls `SetNull()` and discards the fully signed transaction. Re-evaluate the signing action under `cs_coinjoin`, then call `CommitFinalTransaction()` after releasing that lock, just as the accepting-entry action is re-evaluated and finalized.
In `<commit:aaa6d04>`:
- [SUGGESTION] <commit:aaa6d04>:1: Fold the lint-only follow-up into its introducing commits
Commit `aaa6d0464a4` only adds `/* Continued */` markers to four `LogPrint` calls introduced earlier in this stack: one in `96cf3768cab` and three in `7443d022e09`. The linter rejects those unmarked calls, so retaining the separate correction leaves the introducing revisions as lint-failing bisect points and adds review-fix noise to permanent history. Fold each marker into the commit that introduced its call and drop `aaa6d0464a4`.
In `<commit:bb056c6>`:
- [SUGGESTION] <commit:bb056c6>:1: Preserve the rationale for the non-obvious CoinJoin fixes
Commits `bb056c6d397`, `7da61132813`, and `3d5cf6b6e0f` have empty bodies despite changing subtle concurrency and fee-policy invariants. In particular, they serialize timeout reset with finalization, bind entry validation to a session denomination snapshot, and combine guaranteed abort charging with in-flight-message deferral and recoverable-timeout re-evaluation. Add concise bodies explaining the race or policy invariant each commit preserves so ordinary `git log`, blame, and bisect retain the reasoning currently available only from source comments and PR discussion.
3d5cf6b to
9777136
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (4)
src/test/coinjoin_inouts_tests.cpp (4)
750-781: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNarrow the
size_tvalues passed toMakeCollateralandMakePeer.
MakeCollateral(uint32_t)receives thesize_tloop variableiat Line 762, andMakePeer(NodeId, uint32_t)receives0x0a000001 + iat Line 766. Both conversions are implicit narrowing. Add explicit casts so the intent is clear and no compiler in CI reports a conversion warning.♻️ Proposed change
- const auto collateral = MakeCollateral(i); + const auto collateral = MakeCollateral(static_cast<uint32_t>(i)); @@ - auto peer = MakePeer(i, 0x0a000001 + i); + auto peer = MakePeer(static_cast<NodeId>(i), static_cast<uint32_t>(0x0a000001 + i));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 750 - 781, In server_timeout_rechecks_recoverable_session, explicitly narrow the size_t loop variable when passing it to MakeCollateral(uint32_t), and explicitly cast the computed address value passed to MakePeer(NodeId, uint32_t). Preserve the existing loop behavior while eliminating implicit conversion warnings.
188-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd negative lock annotations to the new helpers that take
cs_coinjoin.
SeedParticipantat Line 246 declaresEXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin), andRelayAbortForTestat Line 240 follows that pattern. The other new helpers (ResetForTest,SetTimedOutForTest,AddCollateralForTest,AddEntryForTest,SelectForTest,EnterSigningState,SetFinalTransactionForTest,SeedTimedOutSession,MarkMessageInFlightForTest) also acquirecs_coinjoinbut declare no negative capability. Clang thread-safety analysis then cannot detect a caller that already holdscs_coinjoin.♻️ Example annotation for the affected helpers
- void ResetForTest(PoolState state) + void ResetForTest(PoolState state) EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin) { LOCK(cs_coinjoin);Also applies to: 253-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 188 - 244, Add EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin) annotations to ResetForTest, SetTimedOutForTest, AddCollateralForTest, AddEntryForTest, SelectForTest, EnterSigningState, SetFinalTransactionForTest, SeedTimedOutSession, and MarkMessageInFlightForTest, matching the existing annotations on SeedParticipant and RelayAbortForTest. Keep each helper’s internal cs_coinjoin locking unchanged.
321-424: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider splitting this test case into separate scenarios.
server_abort_fee_selects_unique_offendersverifies nine distinct behaviours in one test case: queue timeout, all-missing submitters, one submitter, partial submitters, all submitters, a lone non-signer, weighting across multiple non-signers, the probabilistic gate, andChargeRandomFees. A failure in an early scenario stops the later ones, and the failure output does not name the scenario. SeparateBOOST_AUTO_TEST_CASEblocks per policy scenario would isolate failures. The server construction can move into a small helper to avoid repetition.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 321 - 424, Split server_abort_fee_selects_unique_offenders into separate BOOST_AUTO_TEST_CASE blocks covering each policy scenario, including timeout handling, submission coverage, non-signer selection and weighting, the probabilistic gate, and ChargeRandomFees. Move shared server setup into a small helper to avoid repetition, and retain each scenario’s existing assertions and setup behavior.
609-624: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer an exact integer constant for the expected mempool delta.
static_cast<CAmount>(0.1 * COIN)uses floating point for a consensus-adjacent amount comparison. UseCOIN / 10to keep the expectation exact and independent of double rounding.♻️ Proposed change
- BOOST_CHECK_EQUAL(delta, static_cast<CAmount>(0.1 * COIN)); + BOOST_CHECK_EQUAL(delta, COIN / 10);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 609 - 624, Replace the floating-point expected amount in the delta assertion after ApplyDelta with the exact integer expression COIN / 10, while preserving the existing CAmount comparison and test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/test/coinjoin_inouts_tests.cpp`:
- Around line 750-781: In server_timeout_rechecks_recoverable_session,
explicitly narrow the size_t loop variable when passing it to
MakeCollateral(uint32_t), and explicitly cast the computed address value passed
to MakePeer(NodeId, uint32_t). Preserve the existing loop behavior while
eliminating implicit conversion warnings.
- Around line 188-244: Add EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin) annotations to
ResetForTest, SetTimedOutForTest, AddCollateralForTest, AddEntryForTest,
SelectForTest, EnterSigningState, SetFinalTransactionForTest,
SeedTimedOutSession, and MarkMessageInFlightForTest, matching the existing
annotations on SeedParticipant and RelayAbortForTest. Keep each helper’s
internal cs_coinjoin locking unchanged.
- Around line 321-424: Split server_abort_fee_selects_unique_offenders into
separate BOOST_AUTO_TEST_CASE blocks covering each policy scenario, including
timeout handling, submission coverage, non-signer selection and weighting, the
probabilistic gate, and ChargeRandomFees. Move shared server setup into a small
helper to avoid repetition, and retain each scenario’s existing assertions and
setup behavior.
- Around line 609-624: Replace the floating-point expected amount in the delta
assertion after ApplyDelta with the exact integer expression COIN / 10, while
preserving the existing CAmount comparison and test behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ec59951d-1248-4c0c-a6ba-d5f91c7b66eb
📒 Files selected for processing (3)
src/coinjoin/server.cppsrc/coinjoin/server.hsrc/test/coinjoin_inouts_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
- src/coinjoin/server.h
- src/coinjoin/server.cpp
|
@PastaPastaPasta this could result in CoinJoin participants being penalized for masternode connectivity issues, right? |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex/Sol only (Phase 2 disabled)
The exact-head Codex revalidation found no actionable defects: the two signing-timeout blockers are fixed, and the rewritten commit stack resolves both history suggestions. All four prior verified findings are resolved at 9777136, so this preliminary gate approves the PR for the next review phase.
Source: reviewer backends: gpt-5.6-sol (general), gpt-5.6-sol (dash-core-commit-history); final verifier backend: gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— dash-core-commit-history (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
- Secondary pass: disabled (
temporary_phase2_sonnet_disable)
9777136 to
4618c53
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4618c53e4b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
4618c53 to
a67fc15
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/coinjoin/server.cpp (1)
536-612: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a unit test for the guaranteed-abort path after a relayed abort.
CheckTimeout()skips collateral selection whenm_relayed_abortis set.RelayStatus(STATUS_REJECTED)and the disconnect path both set that flag. The test helpers shown insrc/test/coinjoin_inouts_tests.cppseed timed-out sessions and relay rejections, so a case that relays a rejection and then asserts that no collateral is consumed is cheap to add.As per coding guidelines: "Add small tests proving invariants when changing consensus, serialization, masternode, LLMQ, InstantSend, ChainLocks, governance, EvoDB, credit-pool, Platform, BLS, quorum prediction, or timing/shutdown behavior."
Also applies to: 666-708
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/coinjoin/server.cpp` around lines 536 - 612, Add a unit test in the existing coinjoin input/output test helpers covering a timed-out session that receives RelayStatus(STATUS_REJECTED), then verify CheckTimeout() takes the guaranteed-abort path without consuming or selecting collateral. Reuse the established session-seeding and relay-rejection setup, and assert the collateral state remains unchanged after the timeout check.Source: Coding guidelines
src/test/coinjoin_inouts_tests.cpp (4)
180-199: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConfirm that
consumed_collateralsis never written from two threads.
consumed_collateralsis a plainmutable std::vectorwith no synchronization. Inserver_timeout_does_not_reset_during_pool_check, a second thread runsHoldPoolCheck, which only takescs_check_pooland does not consume collateral, so the current tests appear safe. If a future test lets a helper thread reachConsumeCollateral, the vector will race. Guarding it withcs_coinjoinor a dedicated mutex would make the invariant explicit.Also applies to: 220-251, 260-345, 819-841
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 180 - 199, The test-only TestableCoinJoinServer::consumed_collaterals vector is unsynchronized and could race if ConsumeCollateral runs on another thread. Protect accesses in ConsumeCollateral, ResetForTest, and all relevant test assertions with cs_coinjoin or a dedicated mutex, preserving the existing lifecycle-test behavior.
355-364: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueClarify the
unsigned_inputssemantics inMakeEntry.
unsigned_inputs == 0produces one signed input, andunsigned_inputs == NproducesNunsigned inputs. The parameter therefore controls both the input count and the signature flag. A short comment or a distinct parameter name would make the call sites at Lines 429, 438, and 539 easier to read.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 355 - 364, Clarify the dual semantics of unsigned_inputs in MakeEntry: zero creates one signed input, while a positive value creates that many unsigned inputs. Add a concise comment or rename the parameter to explicitly communicate this behavior, keeping the existing input-count and fHasSig logic unchanged.
366-486: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated server construction into a fixture helper.
Eight test cases repeat the same
CActiveMasternodeManagerplusTestableCoinJoinServerconstruction with ten identical arguments. A helper that returns the configured server would remove the duplication and keep future constructor changes to one site.Also applies to: 488-524, 526-555, 654-677, 679-718, 720-775, 777-817, 819-841, 843-863, 865-885
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 366 - 486, Extract the repeated CActiveMasternodeManager and TestableCoinJoinServer setup from the affected test cases into a shared fixture helper that returns a configured TestableCoinJoinServer. Update each listed test, including server_abort_fee_selects_unique_offenders, to use the helper while preserving the existing node dependencies and test behavior; keep constructor arguments centralized in that helper.
709-716: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace the duplicated prioritisation amount with the production constant.
The test hardcodes
static_cast<CAmount>(0.1 * COIN). The production code inCommitFinalTransactionapplies the same value. If the production value changes, this assertion silently encodes the old policy. Reference the shared constant instead.🔍 Verification script
#!/bin/bash # Locate the prioritisation delta used by CommitFinalTransaction. rg -nP -C4 'PrioritiseTransaction|0\.1\s*\*\s*COIN' src/coinjoin🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/coinjoin_inouts_tests.cpp` around lines 709 - 716, Update the delta assertion in the coinjoin test to use the shared production prioritisation constant applied by CommitFinalTransaction instead of hardcoding static_cast<CAmount>(0.1 * COIN). Keep the existing delta calculation and validation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/coinjoin/server.cpp`:
- Around line 348-404: Update CCoinJoinServer::CheckPool’s locked snapshot so
the timeout-driven ChargeAndFinalize action is selected only when
m_inflight_session does not equal the current nSessionID. Preserve normal
finalization when all entries are present, but defer charging/finalizing while
the current session has an in-flight submission, matching the guard used by
CheckTimeout().
- Around line 578-587: Update the probabilistic collateral check using
nSessionCollaterals and vecOffendersCollaterals so it cannot evaluate
nSessionCollaterals - 1 when the size is zero. Add an explicit nonzero guard or
use a signed comparison, while preserving the intended “mostly offending”
behavior when entries exist without collaterals.
---
Nitpick comments:
In `@src/coinjoin/server.cpp`:
- Around line 536-612: Add a unit test in the existing coinjoin input/output
test helpers covering a timed-out session that receives
RelayStatus(STATUS_REJECTED), then verify CheckTimeout() takes the
guaranteed-abort path without consuming or selecting collateral. Reuse the
established session-seeding and relay-rejection setup, and assert the collateral
state remains unchanged after the timeout check.
In `@src/test/coinjoin_inouts_tests.cpp`:
- Around line 180-199: The test-only
TestableCoinJoinServer::consumed_collaterals vector is unsynchronized and could
race if ConsumeCollateral runs on another thread. Protect accesses in
ConsumeCollateral, ResetForTest, and all relevant test assertions with
cs_coinjoin or a dedicated mutex, preserving the existing lifecycle-test
behavior.
- Around line 355-364: Clarify the dual semantics of unsigned_inputs in
MakeEntry: zero creates one signed input, while a positive value creates that
many unsigned inputs. Add a concise comment or rename the parameter to
explicitly communicate this behavior, keeping the existing input-count and
fHasSig logic unchanged.
- Around line 366-486: Extract the repeated CActiveMasternodeManager and
TestableCoinJoinServer setup from the affected test cases into a shared fixture
helper that returns a configured TestableCoinJoinServer. Update each listed
test, including server_abort_fee_selects_unique_offenders, to use the helper
while preserving the existing node dependencies and test behavior; keep
constructor arguments centralized in that helper.
- Around line 709-716: Update the delta assertion in the coinjoin test to use
the shared production prioritisation constant applied by CommitFinalTransaction
instead of hardcoding static_cast<CAmount>(0.1 * COIN). Keep the existing delta
calculation and validation unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c9e11abb-3443-427e-ac0d-4a4598969547
📒 Files selected for processing (5)
src/coinjoin/coinjoin.cppsrc/coinjoin/coinjoin.hsrc/coinjoin/server.cppsrc/coinjoin/server.hsrc/test/coinjoin_inouts_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
- src/coinjoin/coinjoin.h
- src/coinjoin/server.h
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The timeout handling still has two in-scope concurrency defects: an on-time in-flight entry can be excluded and probabilistically charged during recoverable finalization, and a selected timeout collateral can be admitted into a replacement session before the penalty transaction consumes it. The unsigned-underflow report is not actionable because the locked production invariants ensure a nonempty offender set always belongs to a session with at least one collateral.
Source: reviewer backend model gpt-5.6-sol (general); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 2 blocking
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/server.cpp`:
- [BLOCKING] src/coinjoin/server.cpp:382-385: Defer timeout finalization while an entry is in flight
`ProcessDSVIN()` marks an on-time entry in flight before deserialization and validation, but this timeout-driven `ChargeAndFinalize` branch does not inspect that marker. If the existing committed entries already meet the minimum, the scheduler can select the still-validating participant as a missing submitter, potentially charge its collateral, and move the session to signing. The pending `AddEntry()` then fails its session-state revalidation, excluding an entry that arrived before the deadline. Require `m_inflight_session != nSessionID` for this timeout branch, matching `CheckTimeout()`; normal finalization when every reservation already has an entry should remain unchanged.
- [BLOCKING] src/coinjoin/server.cpp:703-707: Keep new-session admission closed until the penalty is consumed
`SetNull()` clears the timed-out session and makes the server idle before `ConsumeCollateral()` acquires `cs_main`. During that interval, the message-handling thread can test-accept the selected collateral in `IsAcceptableDSA()` and record it in a replacement session through `CreateNewSession()`. The timeout thread can then consume and relay that collateral, leaving the replacement session with a reservation whose inputs are spent; its participant cannot submit a valid entry, and the new mix can stall or abort. Keep admission closed until consumption finishes, or maintain a pending-penalty prevout set that both new- and existing-session admission reject until the penalty attempt completes.
a67fc15 to
4556612
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The in-flight timeout-finalization defect is fixed at the current head: CheckPool() now defers ChargeAndFinalize while the current session has a message in flight. One blocking race remains because CheckTimeout() exposes the idle state before consuming the selected penalty collateral, allowing that collateral to be reserved by a replacement session before its inputs are spent.
Source: reviewer backend model gpt-5.6-sol (general); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking
1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/server.cpp`:
- [BLOCKING] src/coinjoin/server.cpp:706-710: Keep new-session admission closed until the penalty is consumed
(existing thread: https://github.com/dashpay/dash/pull/7568#discussion_r3770096427)
`SetNull()` clears the timed-out session and exposes `POOL_STATE_IDLE` before `ConsumeCollateral()` acquires `cs_main`. A concurrent `DSACCEPT` can therefore pass `IsAcceptableDSA()` while the selected collateral is still unspent and commit it to a replacement session through `CreateNewSession()`; the timeout thread can then consume that collateral, leaving the replacement session with a reservation whose inputs are already spent. The participant cannot subsequently submit a valid entry, so the replacement mix can stall or abort. Keep admission closed until the consumption attempt completes without introducing a `cs_coinjoin` to `cs_main` lock inversion, or retain the selected collateral's prevouts in a pending-penalty set that both new- and existing-session admission reject until consumption finishes.
4556612 to
118d995
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 118d995b42
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
118d995 to
3fc743f
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
One blocking concurrency defect remains in the signature failure path: a stale DSSIGNFINALTX can relay an abort after the completed session has reset, causing the following session to inherit m_relayed_abort and skip its guaranteed abort charge. The prior admission race is fixed by reserving selected collateral prevouts in m_pending_charges until collateral consumption settles.
Source: reviewer backend model gpt-5.6-sol (general); final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/server.cpp`:
- [BLOCKING] src/coinjoin/server.cpp:294-310: Revalidate the session before relaying a signature abort
The failure path does not verify that the session captured in `session_id` is still the current signing session before charging collateral or calling `RelayStatus(STATUS_REJECTED)`. After an extra or duplicate signature passes the initial state check, `CheckPool()` can observe that all required signatures are complete and commit the session without consulting `m_inflight_session`. If the commit resets the session before this message reaches the failure block, `AddScriptSig()` fails against the cleared entries, the collateral lookup finds nothing, and the unconditional `RelayStatus()` sets `m_relayed_abort` while the server is idle. `CreateNewSession()` does not clear that flag, so the next session inherits it and `CheckTimeout()` suppresses its guaranteed abort charge. Revalidate `nSessionID == *session_id` and `nState == POOL_STATE_SIGNING` under `cs_coinjoin` before mutating abort state.
|
This pull request has conflicts, please rebase. |
|
This pull request has conflicts, please rebase. |
5ec361a to
95b378a
Compare
Yes, with the version you reviewed. It charged one collateral for every session that timed out, so a participant whose message never reached the masternode, or who never got I've redesigned the PR around that:
What's left is inherent and unchanged from develop: if a participant's message is lost on a connection that never visibly drops, the masternode can't tell that from withholding. That case still falls under the probabilistic charge and the everyone-failed exemption. The charge is the collateral fee (0.0001 DASH), never the inputs being mixed. This PR no longer makes that case worse; it only removes the cases where the masternode knows the failure wasn't the participant's. 🤖 Posted autonomously by Claude on behalf of pasta. |
|
Rebuilt this PR on the updated #7537 (head 1d59788, on develop c8bfd7a). It is now #7537's six commits plus three commits of its own:
The previous design charged one collateral for every aborted session. That penalized participants for failures the masternode can't attribute (see the reply above), so it's gone. So are the machinery it needed (in-flight marks, pending-charge reservations, the admission epoch, the saboteur charge), the merge of #7815 and the release notes. The inline threads on those mechanisms refer to code that no longer exists on this branch. This PR no longer depends on #7815, and the duplication with #7599 noted earlier is gone as well. Each new commit has a unit test, and each test was checked to fail with only that commit's 🤖 Posted autonomously by Claude on behalf of pasta. |
25cef5c to
e328cd7
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e328cd7f52
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
e328cd7 to
d85efca
Compare
When a DSSIGNFINALTX input failed AddScriptSig(), the masternode relayed STATUS_REJECTED to every participant of the session. Each client that receives it enters POOL_STATE_ERROR, releases its inputs and stops signing, but the server keeps the session until its signing timeout. There, the clients that obeyed the rejection are the non-signers, and one of them can be charged its collateral, while the participant that sent the bad signature may have signed everything and is not an offender at all. A participant could abort every session it joined this way, by resending a signature it had already submitted, and have an honest peer pay for it. A signature that fails validation comes from the sender, so reject only the sender and keep the session for everyone else. If the sender never supplies a valid signature its input stays unsigned, and the timeout treats it as the non-signer, under the existing probabilistic policy. A duplicate of a signature that was already accepted no longer affects the session at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the masternode cannot deliver the final transaction or a status update to one of the participants, RelayStatus() tells every other participant that the session is terminated. Each client that receives that enters POOL_STATE_ERROR, releases its inputs and stops, so the session can no longer complete. The server only reset the session if *every* participant was unreachable. Otherwise it kept the session until its timeout, where the clients that had obeyed were counted as participants that never sent or signed, and one of them could be charged its collateral. That charged honest participants for a failure the masternode cannot attribute to them: an unreachable peer is just as likely to be the masternode's own connection going down. Once the termination has been relayed, reset the session immediately so its timeout cannot charge anyone. A participant that never sends or signs while everyone is still reachable is handled exactly as before. RelayFinalTransaction() now finishes iterating vecEntries before calling RelayStatus(), which can now clear it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d85efca to
ec816bd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec816bd7fe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ec816bd to
99d7c18
Compare
At the signing timeout, SelectCollateralToCharge() added a participant's collateral once per unsigned input. The offender count is compared with the number of session collaterals to decide whether almost everyone failed (charge only sometimes) or everyone failed (charge nobody, since that points at the masternode rather than the participants). A single participant that withheld signatures on as many inputs as there are collaterals therefore looked like "everyone failed", and could abort the signing phase without ever risking its collateral. A participant with several inputs was also more likely to be picked than one with a single input. Count each participant that left any input unsigned once, and measure the signing offenders against the participants that were asked to sign rather than against every session collateral. A session finalized on timeout moves to signing with only the participants that sent an entry, but keeps the collaterals of those that did not. Compared with all of them, a session in which every participant asked to sign failed did not look like "everyone failed", and one of them could be charged. Per-input counting mostly hid that, except when every signer held a single input. For the same reason the "almost everyone failed" threshold is now one below the number of signers, not one below the number of collaterals. The offender search and the charge-rate thresholds move into the free functions FindSessionOffenders() and GetOffenderChargeRate(), so they can be tested deterministically. SelectCollateralToCharge() keeps the random sampling, the shuffle and the reservation. Its per-offender log lines are replaced by one line with the offender and participant counts. The charging policy itself is unchanged: it is still probabilistic and still exempts a session in which every participant that was expected to act failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99d7c18 to
2f9a7dc
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f9a7dc39f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
Static verification of the complete three-commit diff at 2f9a7dc found no actionable in-scope issues: rejection is sender-local, unreachable-participant termination safely resets the session, and signing offenders are counted once against actual signers. Current CI shows lint and all completed source builds passing; the multiprocess build and test jobs remain pending. The older predict_conflicts check shown as failing by gh was confirmed cancelled, and a newer run passed; it is not a code-check failure.
🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)
Review provenance
Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: dash-core-commit-history); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
normalbygpt-6.1-sol(effort low) — The diff changes collateral-offender selection and session termination in src/coinjoin/server.cpp, but the bounded logic changes are not large or intricate enough to meet the critical tier's joint threshold. - Phase 1 reviewers:
gemini-3.8-flash-high— general (completed, effort high); agentphase1-reviewer - Phase 1 model:
gemini-3.8-flash-high— antigravity quota: weekly 71% left, 5h 100% left - Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— dash-core-commit-history (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— general (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— dash-core-commit-history (completed, effort high); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.
No unresolved findings remain from the prior review on this head.
Issue being fixed or feature implemented
A CoinJoin masternode charges a participant's collateral when the participant does not cooperate: it reserved a slot but never sent its entry, or it never signed the final transaction. The masternode cannot tell a participant that withholds a message from one whose message was lost, so that charge is deliberately probabilistic (about one session in three). Nobody is charged when every participant failed, because that points at the masternode itself.
Three cases broke that balance. In each one, a participant could be charged for a failure that was not its own:
DSSIGNFINALTXinput failed validation, the masternode relayedSTATUS_REJECTEDto every participant. Each client that receives it entersPOOL_STATE_ERROR, releases its inputs and stops signing. The server kept the session until the signing timeout, where the clients that obeyed were the non-signers and one of them could be charged. Meanwhile the sender could already have signed everything. Resending an already accepted signature was enough to make an honest peer pay, every session.An earlier version of this PR made the timeout charge one collateral for every aborted session. As pointed out in review, that would have charged participants with certainty for failures that can come from the masternode's own connectivity. This version drops that policy. The probabilistic charge for a participant that does not cooperate in a running session is unchanged.
What was done?
This PR builds on #7537, which has merged; it is rebased on develop and has three commits:
fix(coinjoin): reject only the sender of an invalid signature: when an input of aDSSIGNFINALTXfailsAddScriptSig(), only the sender getsSTATUS_REJECTED. The session goes on for everyone else. If the sender's signature is still missing at the timeout, the sender is the non-signer there. A duplicate of an already accepted signature no longer affects the session at all.fix(coinjoin): end a session once a participant is unreachable: onceRelayStatus()has told the participants to terminate because one of them is unreachable, it resets the session right away. The reset itself charges nobody. The one exception is the timeout-finalize path: a participant that never sent its entry is picked for charging before the final transaction is relayed, and is still charged if the relay then ends the session, as on develop. Before, it did this only when every participant was unreachable.RelayFinalTransaction()now finishes iterating the entries before it callsRelayStatus(), which can now clear them.fix(coinjoin): count each non-signing participant once:SelectCollateralToCharge()adds a participant once if any of its inputs is unsigned, and measures the signing offenders against the participants that were asked to sign (vecEntries), not against every session collateral. While entries are being accepted the total is still every collateral. The probabilistic policy and the everyone-failed exemption are unchanged. One side effect: in a session finalized on timeout, the "almost everyone failed" threshold (charge only about a third of the time) is now one below the number of signers rather than one below the number of collaterals. The offender search and the thresholds move into the free functionsFindSessionOffenders()andGetOffenderChargeRate()so they can be tested deterministically;SelectCollateralToCharge()keeps the random sampling, the shuffle and the reservation. Its per-offender log lines become one line with the offender and participant counts.What the masternode still charges for, unchanged from develop: a participant that reserved a slot and never sent its entry, or never signed, while the session was running. It charges at most about a third of the time, and never when every participant failed. A message lost on a connection that never visibly drops cannot be told apart from one that was withheld, so that case still falls under the probabilistic policy. This PR does not make it worse. It removes the cases where the masternode knows the failure was on its side or another participant's.
Compared with the previous version of this PR, these are all gone: the guaranteed abort charge, the in-flight message marks, the pending-charge reservations (#7537 now has its own, narrower reservation that only keeps a collateral being charged from being readmitted), the admission epoch, the immediate saboteur charge, the merge of #7815 and the release notes. This PR no longer depends on #7815.
How Has This Been Tested?
macOS arm64, depends build, clang with
-Wthread-safety -Werror, on top of develop fad547f.New unit tests in
coinjoin_inouts_tests. The first two were checked to fail with only their own commit'sserver.cppchange reverted. The third tests functions its commit introduces, so it was checked with mutants instead:server_invalid_signature_rejects_only_its_sender: a participant'sDSSIGNFINALTXfails validation throughProcessMessage(). With-capturemessages, the test checks that only the sender gets aDSSTATUSUPDATE, and that the session stays inSIGNINGwith both entries.server_unreachable_participant_ends_session: the final transaction is relayed byCreateFinalTransaction(). When every participant is reachable, the session moves toSIGNING. When one is not, the session ends right away.server_charges_only_attributable_offenders: a deterministic table overFindSessionOffenders()andGetOffenderChargeRate(). Each row checks which collaterals are found offending, how many participants they are measured against, and the resulting charge rate. Rows cover missing entries while accepting (normal, reduced and none, including no entry at all), a participant leaving all or only some of its inputs unsigned, everyone signed, a session finalized on timeout in which every signer or all signers but one fail, and the queue state. Each of these mutants fails it: counting unsigned inputs instead of participants, measuring signers against every collateral, removing the reduced rate, and removing the everyone-failed exemption.Also run:
test_dashand passescoinjoin_inouts_testson its own.coinjoin_inouts_tests,coinjoin_tests,coinjoin_queue_testsandcoinjoin_dstxmanager_testspass. The functional testsrpc_coinjoin.pyandp2p_dstx.pypassed on the previous base (develop c8bfd7a); on this base they run in CI.lint-whitespace.py,lint-includes.py,lint-circular-dependencies.py,lint-logs.pyandgit diff --checkpass.Breaking Changes
None. No protocol, serialization or RPC change. Two server-side behaviors change. A participant whose signature is rejected now gets the only
STATUS_REJECTED; the other participants no longer receive one. A session in which a participant has become unreachable now ends right away instead of at its timeout, and its participants have already been told to abandon it.Checklist:
🤖 Generated with Claude Code